<?php

namespace core\Models;

use core\Config;
use core\Db;
use core\Debug;
use core\Log;

class BasicModel implements IModel
{

    protected $tablepre = '';
    protected $table = '';
    private $asArray = false;
    private $sql = [];
    private $data = [];
    private $data_d = [];
    private $data_w = [];
    private $data_field = [];
    public $querySql = [];
    public $querySqlData = [];
    public $querySqlParse = [];
    public $queryUseMemory = [];

    /**
     * 表主键
     * @var type 
     */
    protected $pk = '';

    function __construct()
    {
        $this->tablepre = Config::get('DB')['mysql']['prefix'];
        $this->refactor();
    }

    public final function showQuery($query, $params)
    {
        $keys = array();
        $values = array();

        if (!empty($params)) {
            foreach ($params as $key => $value) {
                if (is_string($key)) {
                    $keys[] = '/:' . $key . '/';
                } else {
                    $keys[] = '/[?]/';
                }

                if (is_numeric($value)) {
                    $values[] = $value;
                } else {
                    $values[] = Db::getInstance()->quote($value);
                }
            }
        }

        return preg_replace($keys, $values, $query, 1);
    }

    /**
     * 更新记录
     * 条件没有不作处理
     * 返回所更新的行数
     */
    public final function update($data = array())
    {

        if (empty($this->sql['from'])) {
            $this->from($this->table);
        }
        if (!empty($data)) {
            $this->data($data);
        }

//        if (!empty($this->pk)) {
//            if (isset($this->data[$this->pk])) {
//                $tj[$this->pk] = $this->data[$this->pk];
//                unset($this->data[$this->pk]);
//                $this->where($tj);
//            }
//        }

        $sql = 'UPDATE ' . $this->tablepre . $this->table . ' SET ' . $this->parseData();

        $where = $this->checkWhere();
        if (empty($where)) {
            return false;
        }

        $sql .= $where;
        $res = $this->query($sql, $this->data);
        if (!$res) {
            return false;
        }
        return $res->rowCount();
    }

    public final function data($data = array())
    {
        if (empty($data)) {
            return $this;
        }
        if (is_array($data)) {
            $this->data_d = $data;
        }
        return $this;
    }

    /**
     * 删除记录,可以根据主键(支持单一主键)值来删除
     * $id可以是单个值，也可以是数组
     * 条件为空则不作处理
     * 返回删除的行数(数值型)
     */
    public final function delete($id = '')
    {
        if (empty($this->sql['from'])) {
            $this->from($this->table);
        }
        $sql = 'DELETE ' . $this->sql['from'];
//        if (!empty($id) && !empty($this->pk)) {
//            if (is_array($id)) {
//                $where[$this->pk] = array('IN', $id);
//            } else {
//                $where[$this->pk] = $id;
//            }
//            $this->where($where);
//        }
        $where = $this->checkWhere();
        if (empty($where)) {
            return false;
        }
        $sql .= $where;
        $sql .= $this->sql['limit'];
        $res = $this->query($sql, $this->data_w);
        unset($sql);
        if (!$res) {
            return false;
        }
        return $res->rowCount();
    }

    public final function findByWhere()
    {
        return $this;
    }

    public final function from($form = '')
    {
        $this->sql['from'] = 'FROM `' . $this->tablepre . $form . '`';
        return $this;
    }

    /**
     * 构造sql语句条件
     * $where参数为string,array指定条件元素
     */
    public final function where($where = '')
    {
        if (!empty($where)) {
            if (is_string($where)) {
                $this->sql['where'][] = $where;
            } else {
                $this->sql['where'][] = $this->parseWhere($where);
            }
        }
        return $this;
    }

    private final function parseWhere($where, $index = '')
    {
        if (isset($where['logic'])) {
            $logic = $where['logic'];
            unset($where['logic']);
        } else {
            $logic = 'and';
        }
        $logic = ' ' . strtoupper($logic) . ' ';
        $temp = '';
        foreach ($where as $key => $v) {
            if (is_array($v) && is_array($v[0]) && count($v, 1) != count($v)) {
                $temp .= ($temp ? $logic : '') . '(' . $this->parseWhere($v, $key) . ')';
            } else {
                $v1 = [];
                $index = is_int($key) ? $index : $key;
                if (!is_numeric($index)) {
                    $v1[] = $index;
                }
                if (!is_array($v)) {
                    $v = array('=', $v);
                }
                $arr = array_merge($v1, $v);
                $temp .= ($temp ? $logic : '') . $this->getCondition($arr);
            }
        }
        return $temp;
    }

    /**
     * 处理条件元素
     * $where参数array
     * 返回条件字符串
     */
    private final function getCondition($where = array())
    {
        $count = count($where);
        $whereStr = $value = '';
        if ($count == 3) {
            $where[1] = strtoupper($where[1]);
            switch ($where[1]) {
                case '=':
                case '!=':
                case '>=':
                case '<=':
                case '>':
                case '<':
                case '<>':
                case 'LIKE':
                case 'NOT LIKE':
                case 'OR LIKE':
                case 'OR NOT LIKE':

                    if (strpos($where[0], '.') === false) {
                        $value = ' `' . $where[0] . '` ' . $where[1] . ' ? ';
                    } else {
                        $value = ' ' . $where[0] . ' ' . $where[1] . ' ? ';
                    }

                    $this->data_w[] = $where[2];
                    break;
                case 'BETWEEN':
                case 'NOT BETWEEN':
                    $value = '`' . $where[0] . '` ' . $where[1] . ' ? AND ? ';
                    $this->data_w[] = $where[2][0];
                    $this->data_w[] = $where[2][1];
                    break;
                case 'IN':
                case 'NOT IN':
                    $t = str_pad('', count($where[2]) * 2, ',?');
                    $value = '`' . $where[0] . '` ' . $where[1] . ' (' . ltrim($t, ',') . ')';
                    foreach ($where[2] as $v) {
                        $this->data_w[] = $v;
                    }

                    break;
                default:
                    Log::alert('The first element array is wrong(' . $where[1] . ')');
            }
        }
        return $value;
    }

    /**
     * 新增1条数据
     * @return boolean
     */
    public final function insert($data = [])
    {
        if (empty($this->sql['from'])) {
            $this->from($this->table);
        }
        if (!empty($data)) {
            $this->data($data);
        }
        if (empty($this->data_d)) {
            Debug::errorHandler(8, 'insert  method need data ', __FILE__, __LINE__);
            return false;
        }

        $sql = 'INSERT INTO ' . $this->tablepre . $this->table . ' SET ' . $this->parseData();

        $res = $this->query($sql, array_values($this->data_d));
        if (!$res) {
            return false;
        }
        return Db::getInstance()->lastInsertId();
    }

    /**
     * 批量新增数据
     * @param array $data 数据数组
     * @return number 影响行数
     */
    public final function insertAll($data = [])
    {
        if (count($data, 1) == count($data)) {
            \core\Debug::errorHandler(8, 'insertAll method first argument need array ', __FILE__, __LINE__);
            return false;
        }
        $field = [];
        $values = [];
        $datas = [];
        foreach ($data as $v) {
            if (empty($field)) {
                $field = array_keys($v);
            }
            $s = [];
            foreach ($v as $val) {
                $s[] = '?';
                $datas[] = $val;
            }
            $values[] = '(' . implode(',', $s) . ')';
        }
        $values = join(',', $values);
        $field = '`' . join('`,`', $field) . '`';
        $sql = 'INSERT INTO `' . $this->tablepre . $this->table . '`(' . $field . ')';
        $sql .= ' VALUES ' . $values;
        $res = $this->query($sql, $datas);
        unset($field, $values, $datas);
        if ($res !== false) {
            return $res->rowCount();
        }
        return 0;
    }

    /**
     * 获取数据
     * @param string $key 指定某个字段的值作为数组下标
     * @return type
     */
    public final function all($key = '')
    {
        if (empty($this->sql['from'])) {
            $this->from($this->table);
        }

        $sql = 'SELECT ' . $this->sql['select'];
        $where = $this->checkWhere();
        $sql .= ' ' . $this->sql['from'];
        $sql .= $this->sql['alias'];
        if (!empty($this->sql['join'])) {
            $sql .= join('', $this->sql['join']);
        }
        $sql .= $where;
        $sql .= $this->sql['group'] . $this->checkWhere('having');
        $sql .= ' ' . $this->sql['order'];
        $sql .= ' ' . $this->sql['limit'];
        $sth = $this->query($sql, $this->data_w);
        $res = $sth->fetchAll();
        /**
         * 指定某个字段的值作为数组下标
         */
        if (empty($key)) {
            return $res;
        }

        $temp = [];
        foreach ($res as $k => $v) {
            $temp[$v[$key]] = $v;
        }
        unset($res);
        return $temp;
    }

    /**
     * where 条件处理
     * @param type $key
     * @return string
     */
    private final function checkWhere($key = 'where')
    {
        if (!isset($this->sql[$key]['logic'])) {
            $this->sql[$key]['logic'] = 'and';
        }
        $logic = ' ' . strtoupper($this->sql[$key]['logic']) . ' ';
        unset($this->sql[$key]['logic']);
        $where = '';
        foreach ($this->sql[$key] as $val) {
            $where .= ($where ? $logic : '') . $val;
        }
        if (!empty($where)) {
            $where = ($key == 'where' ? ' WHERE ' : ' HAVING ') . $where;
        }
        return $where;
    }

    public final function asArray($flag = true)
    {
        $this->asArray = $flag;
        return $this;
    }

    /**
     * 计数
     * $field参数字符串，列名
     * 返回数值型
     */
    public final function count($field = '*')
    {
        return $this->aggregate('COUNT', $field);
    }

    /**
     * 求和
     * $field参数字符串，列名
     * 返回数值型
     */
    public final function sum($field)
    {
        return $this->aggregate('SUM', $field);
    }

    /**
     * 求平均值
     * $field参数字符串，列名
     * 返回数值型
     */
    public final function avg($field)
    {
        return $this->aggregate('AVG', $field);
    }

    /**
     * 求最大值
     * $field参数字符串，列名
     * 返回数值型
     */
    public final function max($field)
    {
        return $this->aggregate('MAX', $field);
    }

    /**
     * 求最小值
     * $field参数字符串，列名
     * 返回数值型
     */
    public final function min($field)
    {
        return $this->aggregate('MIN', $field);
    }

    /**
     * 是逻辑与还是逻辑或
     */
    public final function logic($logic = 'AND')
    {
        $logic = strtoupper($logic);
        $this->logic = ' ' . $logic . ' ';
        return $this;
    }

    /**
     * 字段值自增
     * $data参数为array,指定列名
     * 返回所影响的行数
     */
    public final function increment($data = [])
    {
        $t = [];
        foreach ($data as $k => $v) {
            $t[$k] = ['+', $v];
        }
        return $this->update($t);
    }

    /**
     * 字段值自减
     * $data参数为array,指定列名
     * 返回所影响的行数
     */
    public final function decrement($data = [])
    {

        $t = [];
        foreach ($data as $k => $v) {
            $t[$k] = ['-', $v];
        }
        return $this->update($t);
    }

    /**
     * 聚合函数处理
     * $fn参数string指定聚合函数名SUM,COUNT,AVG,MAX,MIN
     * $field参数string指定列名
     * 返回数值型
     */
    private final function aggregate($fn, $field)
    {
        if (empty($this->sql['from'])) {
            $this->from($this->table);
        }
        $sql = 'SELECT ' . $fn . '(' . $field . ') as field ' . $this->sql['from'];
        $sql .= $this->sql['alias'];
        if (!empty($this->sql['join'])) {
            $sql .= join('', $this->sql['join']);
        }
        $where = $this->checkWhere();
        $sql .= $where;
        $res = $this->query($sql, $this->data_w);
        if (!$res) {
            return false;
        }
        return $res->fetch()->field;
    }

    /**
     * sql过滤
     * $str参数string，要处理的sql内容
     * 返回string
     */
    public final function quote($str = '')
    {
        if (empty($str)) {
            return '';
        }
        return Db::getInstance()->quote($str);
    }

    /**
     * sql select处理
     * @param type $select
     * @param type $flag
     * @return $this
     */
    public final function select($select = "*", $flag = false)
    {
        if (is_string($select)) {
            if (trim($select) == '*') {
                $this->sql['select'] = $select;
                return $this;
            }
            $select = explode(',', $select);
        }
        $this->sql['select'] = join(',', $select);
        return $this;
    }

    /**
     * 执行操作
     * @param string $sql 原始预处理sql语句
     * @param array $data 需要替换的预处理sql语句数据
     * @return obj
     */
    public final function query($sql = '', $data = [])
    {
        if (Config::get('debug')) {
            $tsql = $this->showQuery($sql, $data);
            Log::info('SQL', $tsql);
            Debug::setSql($tsql);
            $this->querySqlParse[] = $tsql;
            $mem = memory_get_usage();
        }
        $this->querySql[] = $sql;
        $this->querySqlData[] = $data;

        $sth = Db::getInstance()->prepare($sql);
        if (Db::getInstance()->errorCode() != '00000') {
            $e = Db::getInstance()->errorInfo();
            /**
             * 正式环境中 出错的 sql 也写入 log
             */
            Log::info('SQL', $sql);
            $tsql = $this->showQuery($sql, $data);
            Log::info('SQL', $tsql);
            Log::alert('SQL Error', $e[2]);
            Log::alert('Trace', ['trace' => debug_backtrace()]);
        }

        $sth->execute($data);

        if ($this->asArray) {
            $sth->setFetchMode(\PDO::FETCH_ASSOC);
        } else {
            $sth->setFetchMode(\PDO::FETCH_OBJ);
        }
        if (Config::get('debug')) {
            $this->queryUseMemory[] = round((memory_get_usage() - $mem) / 1024 / 1024, 2) . 'M';
        }
        $this->refactor();
        return $sth;
    }

    /**
     * 自定义表处理
     * @param string $form
     * @param string $table_pre 自定义表前缀
     * @return $this
     */
    public final function table($form = '', $table_pre = '')
    {
        if (!empty($table_pre)) {
            $this->tablepre = $table_pre;
        }
        $this->sql['from'] = 'FROM `' . $this->tablepre . $form . '`';
        $this->table = $form;
        return $this;
    }

    /**
     * 查询单条记录，或者某个字段的值
     * $flag参数boolean当查询一个字段时是否返回具体的值
     * 返回一维数组或者一个对象或者标量
     */
    public final function one($flag = false)
    {
        if (empty($this->sql['from'])) {
            $this->from($this->table);
        }
        $sql = 'SELECT ' . $this->sql['select'];
        $where = $this->checkWhere();
        $sql .= ' ' . $this->sql['from'];
        $sql .= $this->sql['alias'];
        if (!empty($this->sql['join'])) {
            $sql .= join('', $this->sql['join']);
        }
        $sql .= $where;
        $sql .= $this->sql['group'] . $this->checkWhere('having');
        $sql .= ' ' . $this->sql['order'];
        $sql .= ' ' . $this->sql['limit'];
        $sth = $this->query($sql, $this->data_w);
        //返回一个具体值
        if ($flag) {
            return current($sth->fetch());
        }
        return $sth->fetch();
    }

    /**
     * 查询排序
     * $orderby参数string
     */
    public final function order($orderby = '')
    {
        if (empty($orderby)) {
            return $this;
        }
        $this->sql['order'] = 'ORDER BY ' . $orderby;
        return $this;
    }

    /**
     * 获取数量
     * @param string $limit
     * @return $this
     */
    public final function limit($limit = 10)
    {
        if (empty($limit)) {
            return $this;
        }
        if (is_array($limit)) {
            $limit = (int) $limit[0] . ',' . (int) $limit[1];
        }
        $this->sql['limit'] = 'LIMIT ' . $limit;
        return $this;
    }

    /**
     * 指行原始sql语句,适合执行复杂查询的sql语句
     * 有数据返回二维数组，没有则返回空数组
     */
    public final function execute($sql)
    {
        $result = $this->query($sql);
        $lists = array();
        while (($temp = $result->fetch(\PDO::FETCH_ASSOC)) !== false) {
            $lists[] = $temp;
        }
        return $lists;
    }

    public final function alias($name)
    {
        $this->sql['alias'] = ' AS ' . $name;
        return $this;
    }

    /**
     * 左,右,内连接
     * @param type $join
     * @return $this
     */
    public final function join($join = '')
    {
        if (!empty($join)) {
            $this->sql['join'][] = ' ' . $join . ' ';
        }
        return $this;
    }

    /**
     * 解析data
     * @return type
     */
    private final function parseData()
    {
        $t = '';
        $sql_operation = ['-', '+', '*', '/', '%'];
        if (!empty($this->data_d)) {
            foreach ($this->data_d as $k => $v) {
                if (is_array($v)) {
                    if ((empty($v[0]) || empty($v[1]))) {
                        unset($this->data_d[$k]);
                        continue;
                    }
                    if ($v[0] == 'F') {
                        $t[$k] = "`{$k}`={$v[1]}";
                        unset($this->data_d[$k]);
                    } else if (in_array($v[0], $sql_operation)) {
                        $t[$k] = "`{$k}`=`{$k}`{$v[0]}?";
                    } else {
                        unset($this->data_d[$k]);
                        continue;
                    }
                } else {
                    $t[$k] = "`{$k}`=?";
                }
                if (array_key_exists($k, $this->data_d)) {
                    $this->data[] = is_array($this->data_d[$k]) ? $this->data_d[$k][1] : $this->data_d[$k];
                }
            }
        }
        /**
         * 解析where 条件的Data
         */
        if (!empty($this->data_w)) {
            foreach ($this->data_w as $k => $v) {
                $this->data[] = $v;
            }
        }
        return join(',', $t);
    }

    /**
     * 初始化 sql 参数集合
     */
    private final function refactor()
    {
        $this->data = [];
        $this->data_w = [];
        $this->data_d = [];
        $this->sql['where'] = [];
        $this->sql['group'] = '';
        $this->sql['limit'] = '';
        $this->sql['order'] = '';
        $this->sql['having'] = [];
        $this->sql['join'] = [];
        $this->sql['alias'] = '';
        $this->sql['select'] = '*';
        $this->asArray = false;
    }

}
